package net.fornwall.eclipsecoder.haskell;

import java.lang.reflect.Array;

import net.fornwall.eclipsecoder.stats.CodeGenerator;
import net.fornwall.eclipsecoder.stats.ProblemStatement;
import net.fornwall.eclipsecoder.stats.ProblemStatement.TestCase;

public class HaskellCodeGenerator extends CodeGenerator {

    public HaskellCodeGenerator(ProblemStatement statement) {
        super(statement);
    }

    @Override
    public String getDummyReturnString() {
        Class returnType = problemStatement.getReturnType().getClass();
        if (returnType.isArray()) {
            return "[]";
        } else if (returnType.equals(Character.class) || returnType.equals(String.class)) {
            return "\"\"";
        } else {
            return "1";
        }
    }

    @Override
    public String getTestsSource() {
        StringBuilder builder = new StringBuilder();
        builder.append("module Main (main) where \n\n");
        builder.append("import " + problemStatement.getClassName() + " ("
                + problemStatement.getMethodName() + ")\n");
        builder.append("import IO (ioeGetErrorString)\n");
        builder.append("import Monad (unless)\n\n");

        // lifted from HUnit:
        builder.append("type Assertion = IO ()\n");
        builder.append("assertFailure :: String -> Assertion\n");
        builder.append("assertFailure msg = ioError (userError msg)\n");
        builder.append("assertEquals :: (Eq a, Show a) => String -> a -> a -> Assertion\n");
        builder.append("assertEquals preface expected actual =\n");
        builder.append("    unless (actual == expected) (assertFailure msg)\n");
        builder
                .append("    where msg = (if null preface then \"\" else preface ++ \" FAILED! \") ++\n");
        builder
                .append("                 \"Expected '\" ++ show expected ++ \"' but was '\" ++ show actual ++ \"'\"\n\n");
        builder.append("runTests [] = return ()\n");
        builder.append("runTests (x:xs) = catch x (\\e -> putStrLn (ioeGetErrorString e)) >>\n");
        builder.append("    runTests xs\n\n");

        int testCount = -1;
        for (TestCase testCase : problemStatement.getTestCases()) {
            testCount++;
            builder.append("test_" + testCount + " = assertEquals \"Test " + testCount + "\"");
            builder.append(" ");
            builder.append(getCreateStatement(testCase.returnValue));
            builder.append(" (");
            builder.append(problemStatement.getMethodName());
            for (Object parameter : testCase.getParameters()) {
                builder.append(" ");
                builder.append(getCreateStatement(parameter));
            }
            builder.append(")\n");
        }

        builder.append("\nmain = do\n    runTests [");
        for (int i = 0; i < problemStatement.getTestCases().size(); i++) {
            if (i != 0)
                builder.append(", ");
            builder.append("test_" + i);
        }
        builder.append("]\n    putStrLn \"Done! All tests executed\"\n");

        return builder.toString();
    }

    @Override
    public String getTypeString(Class type) {
        return "";
    }

    private String getCreateStatement(Object value) {
        if (value.getClass().isArray()) {
            int length = Array.getLength(value);
            StringBuilder result = new StringBuilder("[");
            for (int i = 0; i < length; i++) {
                if (i != 0)
                    result.append(", ");
                result.append(getSimpleCreateStatement(Array.get(value, i)));
            }
            result.append("]");
            return result.toString();
        } else {
            return getSimpleCreateStatement(value);
        }
    }

    @Override
    protected String getMethodParamsString() {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < problemStatement.getParameterNames().size(); i++) {
            if (i != 0)
                result.append(" ");
            String paramName = problemStatement.getParameterNames().get(i);
            if (Character.isUpperCase(paramName.charAt(0))) {
                // haskell variable names may not start with an upper case letter
                paramName = Character.toLowerCase(paramName.charAt(0)) + paramName.substring(1);
            }
            result.append(paramName);
        }
        return result.toString();
    }

    /** One-dimensional */
    private String getSimpleCreateStatement(Object value) {
        if (value.getClass() == Character.class) {
            return "\"" + value + "\"";
        } else if (value.getClass() == String.class) {
            return '"' + value.toString().replaceAll("\\\\", "\\\\\\\\") + '"';
        } else {
            return value.toString() + ((value instanceof Long) ? "L" : "");
        }
    }

}
